/*
 * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.nulldev.util.graphics.renderIt.ginterfaces.marlin.impl.pisces;

import static com.nulldev.util.graphics.renderIt.ginterfaces.marlin.impl.pisces.MarlinUtils.logInfo;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import java.util.concurrent.ConcurrentLinkedQueue;

import com.nulldev.util.graphics.renderIt.ginterfaces.marlin.impl.pisces.ArrayCacheConst.CacheStats;
import com.nulldev.util.graphics.renderIt.ginterfaces.marlin.impl.pisces.stats.ArraySortDataCollection;
import com.nulldev.util.graphics.renderIt.ginterfaces.marlin.impl.pisces.stats.Histogram;
import com.nulldev.util.graphics.renderIt.ginterfaces.marlin.impl.pisces.stats.Monitor;
import com.nulldev.util.graphics.renderIt.ginterfaces.marlin.impl.pisces.stats.StatLong;

/**
 * This class gathers global rendering statistics for debugging purposes only
 */
public final class RendererStats implements MarlinConst {

	@SuppressWarnings("unused")
	public final static boolean DUMP_ARRAY_DATA = false && MarlinConst.DO_STATS;

	static RendererStats createInstance(final Object parent, final String name) {
		final RendererStats stats = new RendererStats(name);

		// Keep a strong reference to dump it later:
		RendererStatsHolder.getInstance().add(parent, stats);

		return stats;
	}

	public static void dumpStats() {
		RendererStatsHolder.dumpStats();
	}

	public static ArraySortDataCollection getADC() {
		return RendererStatsHolder.getADC();
	}

	// context name (debugging purposes)
	final String name;
	// stats
	final StatLong stat_cache_rowAA = new StatLong("cache.rowAA");
	final StatLong stat_cache_rowAAChunk = new StatLong("cache.rowAAChunk");
	final StatLong stat_cache_tiles = new StatLong("cache.tiles");
	final StatLong stat_rdr_addLine = new StatLong("renderer.addLine");
	final StatLong stat_rdr_addLine_skip = new StatLong("renderer.addLine.skip");
	final StatLong stat_rdr_curveBreak = new StatLong("renderer.curveBreakIntoLinesAndAdd");
	final StatLong stat_rdr_curveBreak_dec = new StatLong("renderer.curveBreakIntoLinesAndAdd.dec");
	final StatLong stat_rdr_curveBreak_inc = new StatLong("renderer.curveBreakIntoLinesAndAdd.inc");
	final StatLong stat_rdr_quadBreak = new StatLong("renderer.quadBreakIntoLinesAndAdd");
	final StatLong stat_rdr_quadBreak_dec = new StatLong("renderer.quadBreakIntoLinesAndAdd.dec");
	final StatLong stat_rdr_edges = new StatLong("renderer.edges");
	final StatLong stat_rdr_edges_count = new StatLong("renderer.edges.count");
	final StatLong stat_rdr_edges_resizes = new StatLong("renderer.edges.resize");
	final StatLong stat_rdr_activeEdges = new StatLong("renderer.activeEdges");
	final StatLong stat_rdr_activeEdges_updates = new StatLong("renderer.activeEdges.updates");
	final StatLong stat_rdr_activeEdges_adds = new StatLong("renderer.activeEdges.adds");
	final StatLong stat_rdr_activeEdges_adds_high = new StatLong("renderer.activeEdges.adds_high");
	final StatLong stat_rdr_crossings_updates = new StatLong("renderer.crossings.updates");
	final StatLong stat_rdr_crossings_sorts = new StatLong("renderer.crossings.sorts");
	final StatLong stat_rdr_crossings_bsearch = new StatLong("renderer.crossings.bsearch");
	final StatLong stat_rdr_crossings_msorts = new StatLong("renderer.crossings.msorts");
	final StatLong stat_rdr_crossings_dpqs = new StatLong("renderer.crossings.dpqs");
	final StatLong stat_str_polystack_curves = new StatLong("stroker.polystack.curves");
	final StatLong stat_str_polystack_types = new StatLong("stroker.polystack.types");
	final StatLong stat_cpd_polystack_curves = new StatLong("closedPathDetector.polystack.curves");
	final StatLong stat_cpd_polystack_types = new StatLong("closedPathDetector.polystack.types");
	final StatLong stat_pcf_idxstack_indices = new StatLong("pathClipFilter.stack.indices");
	// growable arrays
	final StatLong stat_array_dasher_dasher = new StatLong("array.dasher.dasher.d_float");
	final StatLong stat_array_dasher_firstSegmentsBuffer = new StatLong("array.dasher.firstSegmentsBuffer.d_float");
	final StatLong stat_array_marlincache_rowAAChunk = new StatLong("array.marlincache.rowAAChunk.resize");
	final StatLong stat_array_marlincache_touchedTile = new StatLong("array.marlincache.touchedTile.int");
	final StatLong stat_array_renderer_alphaline = new StatLong("array.renderer.alphaline.int");
	final StatLong stat_array_renderer_crossings = new StatLong("array.renderer.crossings.int");
	final StatLong stat_array_renderer_aux_crossings = new StatLong("array.renderer.aux_crossings.int");
	final StatLong stat_array_renderer_edgeBuckets = new StatLong("array.renderer.edgeBuckets.int");
	final StatLong stat_array_renderer_edgeBucketCounts = new StatLong("array.renderer.edgeBucketCounts.int");
	final StatLong stat_array_renderer_edgePtrs = new StatLong("array.renderer.edgePtrs.int");
	final StatLong stat_array_renderer_aux_edgePtrs = new StatLong("array.renderer.aux_edgePtrs.int");
	final StatLong stat_array_str_polystack_curves = new StatLong("array.stroker.polystack.curves.d_float");
	final StatLong stat_array_str_polystack_types = new StatLong("array.stroker.polystack.curveTypes.d_byte");
	final StatLong stat_array_cpd_polystack_curves = new StatLong("array.closedPathDetector.polystack.curves.d_float");
	final StatLong stat_array_cpd_polystack_types = new StatLong("array.closedPathDetector.polystack.curveTypes.d_byte");
	final StatLong stat_array_pcf_idxstack_indices = new StatLong("array.pathClipFilter.stack.indices.d_int");
	// histograms
	final Histogram hist_rdr_edges_count = new Histogram("renderer.edges.count");
	final Histogram hist_rdr_crossings = new Histogram("renderer.crossings");
	final Histogram hist_rdr_crossings_ratio = new Histogram("renderer.crossings.ratio");
	final Histogram hist_rdr_crossings_adds = new Histogram("renderer.crossings.adds");
	final Histogram hist_rdr_crossings_msorts = new Histogram("renderer.crossings.msorts");
	final Histogram hist_rdr_crossings_msorts_adds = new Histogram("renderer.crossings.msorts.adds");
	final Histogram hist_str_polystack_curves = new Histogram("stroker.polystack.curves");
	final Histogram hist_tile_generator_alpha = new Histogram("tile_generator.alpha");
	final Histogram hist_tile_generator_encoding = new Histogram("tile_generator.encoding");
	final Histogram hist_tile_generator_encoding_dist = new Histogram("tile_generator.encoding.dist");
	final Histogram hist_tile_generator_encoding_ratio = new Histogram("tile_generator.encoding.ratio");
	final Histogram hist_tile_generator_encoding_runLen = new Histogram("tile_generator.encoding.runLen");
	final Histogram hist_cpd_polystack_curves = new Histogram("closedPathDetector.polystack.curves");
	final Histogram hist_pcf_idxstack_indices = new Histogram("pathClipFilter.stack.indices");
	// all stats
	final StatLong[] statistics = new StatLong[]
		{ stat_cache_rowAA, stat_cache_rowAAChunk, stat_cache_tiles, stat_rdr_addLine, stat_rdr_addLine_skip, stat_rdr_curveBreak, stat_rdr_curveBreak_dec,
				stat_rdr_curveBreak_inc, stat_rdr_quadBreak, stat_rdr_quadBreak_dec, stat_rdr_edges, stat_rdr_edges_count, stat_rdr_edges_resizes,
				stat_rdr_activeEdges, stat_rdr_activeEdges_updates, stat_rdr_activeEdges_adds, stat_rdr_activeEdges_adds_high, stat_rdr_crossings_updates,
				stat_rdr_crossings_sorts, stat_rdr_crossings_bsearch, stat_rdr_crossings_msorts, stat_rdr_crossings_dpqs, stat_str_polystack_types,
				stat_str_polystack_curves, stat_cpd_polystack_curves, stat_cpd_polystack_types, stat_pcf_idxstack_indices, hist_rdr_edges_count,
				hist_rdr_crossings, hist_rdr_crossings_ratio, hist_rdr_crossings_adds, hist_rdr_crossings_msorts, hist_rdr_crossings_msorts_adds,
				hist_tile_generator_alpha, hist_tile_generator_encoding, hist_tile_generator_encoding_dist, hist_tile_generator_encoding_ratio,
				hist_tile_generator_encoding_runLen, hist_str_polystack_curves, hist_cpd_polystack_curves, hist_pcf_idxstack_indices, stat_array_dasher_dasher,
				stat_array_dasher_firstSegmentsBuffer, stat_array_marlincache_rowAAChunk, stat_array_marlincache_touchedTile, stat_array_renderer_alphaline,
				stat_array_renderer_crossings, stat_array_renderer_aux_crossings, stat_array_renderer_edgeBuckets, stat_array_renderer_edgeBucketCounts,
				stat_array_renderer_edgePtrs, stat_array_renderer_aux_edgePtrs, stat_array_str_polystack_curves, stat_array_str_polystack_types,
				stat_array_cpd_polystack_curves, stat_array_cpd_polystack_types, stat_array_pcf_idxstack_indices };
	// monitors
	final Monitor mon_pre_getAATileGenerator = new Monitor("MarlinRenderingEngine.getAATileGenerator()");
	final Monitor mon_rdr_addLine = new Monitor("Renderer.addLine()");
	final Monitor mon_rdr_endRendering = new Monitor("Renderer.endRendering()");
	final Monitor mon_rdr_endRendering_Y = new Monitor("Renderer._endRendering(Y)");
	final Monitor mon_rdr_copyAARow = new Monitor("Renderer.copyAARow()");
	final Monitor mon_pipe_renderTiles = new Monitor("AAShapePipe.renderTiles()");
	final Monitor mon_ptg_getAlpha = new Monitor("MarlinTileGenerator.getAlpha()");
	final Monitor mon_debug = new Monitor("DEBUG()");
	// all monitors
	final Monitor[] monitors = new Monitor[]
		{ mon_pre_getAATileGenerator, mon_rdr_addLine, mon_rdr_endRendering, mon_rdr_endRendering_Y, mon_rdr_copyAARow, mon_pipe_renderTiles, mon_ptg_getAlpha,
				mon_debug };
	// offheap stats
	long totalOffHeapInitial = 0L;
	// live accumulator
	long totalOffHeap = 0L;
	long totalOffHeapMax = 0L;
	// cache stats
	CacheStats[] cacheStats = null;

	private RendererStats(final String name) {
		this.name = name;
	}

	void dump() {
		logInfo("RendererContext: " + name);

		if (DO_MONITORS) {
			for (Monitor monitor : monitors) {
				if (monitor.count != 0) {
					logInfo(monitor.toString());
				}
			}
			// As getAATileGenerator percents:
			final long total = mon_pre_getAATileGenerator.sum;
			if (total != 0L) {
				for (Monitor monitor : monitors) {
					logInfo(monitor.name + " : " + ((100d * monitor.sum) / total) + " %");
				}
			}
			if (DO_FLUSH_MONITORS) {
				for (Monitor m : monitors) {
					m.reset();
				}
			}
		}

		if (DO_STATS) {
			for (StatLong stat : statistics) {
				if (stat.count != 0) {
					logInfo(stat.toString());
					if (DO_FLUSH_STATS) {
						stat.reset();
					}
				}
			}

			logInfo("OffHeap footprint: initial: " + totalOffHeapInitial + " bytes - max: " + totalOffHeapMax + " bytes");
			if (DO_FLUSH_STATS) {
				totalOffHeapMax = 0L;
			}

			logInfo("Array caches for RendererContext: " + name);

			long totalInitialBytes = totalOffHeapInitial;
			long totalCacheBytes = 0L;

			if (cacheStats != null) {
				for (CacheStats stat : cacheStats) {
					totalCacheBytes += stat.dumpStats();
					totalInitialBytes += stat.getTotalInitialBytes();
					if (DO_FLUSH_STATS) {
						stat.reset();
					}
				}
			}
			logInfo("Heap footprint: initial: " + totalInitialBytes + " bytes - cache: " + totalCacheBytes + " bytes");
		}
	}

	static final class RendererStatsHolder {

		// singleton
		private static volatile RendererStatsHolder SINGLETON = null;

		static synchronized RendererStatsHolder getInstance() {
			if (SINGLETON == null) {
				SINGLETON = new RendererStatsHolder();
			}
			return SINGLETON;
		}

		static void dumpStats() {
			if (SINGLETON != null) {
				SINGLETON.dump();
			}
		}

		static ArraySortDataCollection getADC() {
			if (SINGLETON != null) {
				return SINGLETON.adc;
			}
			return null;
		}

		/*
		 * RendererStats collection as hard references (only used for debugging
		 * purposes)
		 */
		private final ConcurrentLinkedQueue<RendererStats> allStats = new ConcurrentLinkedQueue<RendererStats>();
		// array data
		final ArraySortDataCollection adc = new ArraySortDataCollection();

		private RendererStatsHolder() {
			AccessController.doPrivileged(new PrivilegedAction<Void>() {

				@Override
				public Void run() {
					final Thread hook = new Thread(MarlinUtils.getRootThreadGroup(), new Runnable() {
						@Override
						public void run() {
							dump();

							// dump array data:
							ArraySortDataCollection.save("/tmp/ArraySortDataCollection.ser", adc);
						}
					}, "MarlinStatsHook");
					hook.setContextClassLoader(null);
					Runtime.getRuntime().addShutdownHook(hook);

					if (USE_DUMP_THREAD) {
						final Timer statTimer = new Timer("RendererStats");
						statTimer.scheduleAtFixedRate(new TimerTask() {
							@Override
							public void run() {
								dump();
							}
						}, DUMP_INTERVAL, DUMP_INTERVAL);
					}
					return null;
				}
			});

			// Mimics Java2D Disposer:
			AccessController.doPrivileged(new PrivilegedAction<Void>() {

				@Override
				public Void run() {
					/*
					 * The thread must be a member of a thread group which will not get GCed before
					 * VM exit. Make its parent the top-level thread group.
					 */
					final Thread t = new Thread(MarlinUtils.getRootThreadGroup(), new RendererStatsDisposer(), "MarlinRenderer Disposer");
					t.setContextClassLoader(null);
					t.setDaemon(true);
					t.setPriority(Thread.MAX_PRIORITY - 2);
					t.start();
					return null;
				}
			});
		}

		void add(final Object parent, final RendererStats stats) {
			allStats.add(stats);

			// Create the phantom reference to ensure removing dead entries:
			REF_LIST.add(new RendererStatsReference(parent, stats));
		}

		void remove(final RendererStats stats) {
			stats.dump(); // dump anyway
			allStats.remove(stats);
		}

		void dump() {
			for (RendererStats stats : allStats) {
				stats.dump();
			}
		}

		// Custom disposer (replaced by jdk9 Cleaner)

		// Parent reference queue
		private static final ReferenceQueue<Object> REF_QUEUE = new ReferenceQueue<Object>();
		// reference list
		private static final Vector<RendererStatsReference> REF_LIST = new Vector<RendererStatsReference>(32);

		static final class RendererStatsReference extends PhantomReference<Object> {

			private final RendererStats stats;

			RendererStatsReference(final Object parent, final RendererStats stats) {
				super(parent, REF_QUEUE);
				this.stats = stats;
			}

			void dispose() {
				// remove stats from allRdrStats
				RendererStatsHolder.getInstance().remove(this.stats);
			}
		}

		static final class RendererStatsDisposer implements Runnable {
			@Override
			public void run() {
				final Thread currentThread = Thread.currentThread();
				RendererStatsReference ref;

				// check interrupted:
				for (; !currentThread.isInterrupted();) {
					try {
						ref = (RendererStatsReference) REF_QUEUE.remove();
						ref.dispose();

						REF_LIST.remove(ref);

					} catch (InterruptedException ie) {
						MarlinUtils.logException("RendererStatsDisposer interrupted:", ie);
					}
				}
			}
		}
	}
}
